require "io"
require "string"
require "console"
require "screen"
require "controls"

function console_puts(str,x,y)
  if x==nil then
    io.stdout:write(str)
  else
    io.stdout:write(string.format("\x1b[%d;%dH",y,x), str)
  end
end

function readLines(fname)
  local lines = {}
  local numLines = 0

  for line in io.lines(fname) do
    numLines = numLines + 1
    lines[numLines] = line .. " "
  end

  return numLines, lines
end

function saveAs(editor, fname)
  local l = 1
  local fd = io.open(fname,"w")

  while l<= editor.numLines do
    local line = editor.lines[l]
    if string.len(line)>1 then
      line = string.sub(line, 1, -2)
    end
    
    fd:write(line)
    fd:write("\n")

    l = l + 1
  end

  io.close(fd)
end

function save(editor)
  saveAs(editor, editor.fname)
end

function printOptions(options, numOptions, selectedIndex)
  local maxLen = 6 --CANCEL

  local i = 1
  while i<=numOptions do
    local len = string.len(options[i])
    if len>maxLen then
      maxLen = len
    end
    i = i + 1
  end

  local hline = "\x1b[7m" .. string.rep(" ",maxLen+2) .. "\x1b[27m\n"
  console_puts(hline,0,0)

  local i = 1
  while i<=numOptions do
    local optString = options[i]..string.rep(" ",maxLen-string.len(options[i]))
    if i==selectedIndex then
      console_puts("\x1b[7m "..optString.." \x1b[27m\n")
    else
      console_puts("\x1b[7m \x1b[27m"..optString.."\x1b[7m \x1b[27m\n")
    end
    i = i + 1
  end

  console_puts(hline)
end

function updateColLine(editor)
  local hMargin = 5
  local vMargin = 3
  
  if (editor.cursorCol-hMargin) < editor.col then
    editor.col = editor.cursorCol - hMargin
    if editor.col<1 then editor.col = 1 end
  end   
      
  if (editor.cursorCol+hMargin) > (editor.col+editor.colsPerLine) then
    editor.col = editor.cursorCol+hMargin - editor.colsPerLine
  end
  
  if (editor.cursorLine-vMargin) < editor.line then
    editor.line = editor.cursorLine - vMargin
    if editor.line<1 then editor.line = 1 end
  end
      
  if (editor.cursorLine+vMargin) > (editor.line+editor.linesPerScreen) then
    editor.line = editor.cursorLine+vMargin - editor.linesPerScreen
  end
      
end

function moveToEndOfLine(editor)
  local line = editor.lines[editor.cursorLine]   
  local lineLen = string.len(line)
  local tabByte = string.byte("\t")
     
  while editor.lineOffset<lineLen do
    if string.byte(line,editor.lineOffset) == tabByte then
      editor.cursorCol = editor.cursorCol + editor.numTabSpaces
    else
      editor.cursorCol = editor.cursorCol + 1
    end
    editor.lineOffset = editor.lineOffset + 1
  end
  updateColLine(editor)
end

function moveLeft(editor) 
  local line = editor.lines[editor.cursorLine]
  local tabByte = string.byte("\t")
  
  if editor.cursorCol>1 then
    if string.byte(line,editor.lineOffset-1) == tabByte then
      editor.cursorCol = editor.cursorCol - editor.numTabSpaces
    else
      editor.cursorCol = editor.cursorCol - 1
    end
    editor.lineOffset = editor.lineOffset - 1
    updateColLine(editor)
  else
    if editor.cursorLine>1 then
      editor.lineOffset = 1
      editor.cursorCol = 1
      editor.cursorLine = editor.cursorLine - 1
      moveToEndOfLine(editor)
    end
  end
end

function moveRight(editor)
  local line = editor.lines[editor.cursorLine]
  local tabByte = string.byte("\t")
  
  if editor.lineOffset<string.len(line) then
    if string.byte(line,editor.lineOffset) == tabByte then
      editor.cursorCol = editor.cursorCol + editor.numTabSpaces
    else
      editor.cursorCol = editor.cursorCol + 1
    end
    editor.lineOffset = editor.lineOffset + 1
    updateColLine(editor)
  else
    if editor.cursorLine<editor.numLines then
      editor.lineOffset = 1
      editor.cursorCol = 1
      editor.cursorLine = editor.cursorLine + 1
      updateColLine(editor)
    end
  end
end

function updatePositionsFromCursorPos(editor) 
  local line = editor.lines[editor.cursorLine]
  local lineLen = string.len(line)
  
  local pos = editor.cursorCol
      
  editor.lineOffset = 1
  editor.cursorCol = 1
       
  local tabByte = string.byte("\t")
  local onTab = 0
  while (pos > 1) and (editor.lineOffset < lineLen) do
    if onTab > 0 then
      if onTab==1 then
        editor.lineOffset = editor.lineOffset + 1
        editor.cursorCol = editor.cursorCol + editor.numTabSpaces
      end
      onTab = onTab - 1
    else
      if string.byte(line,editor.lineOffset) == tabByte then
        onTab = editor.numTabSpaces-1
      else
        editor.lineOffset = editor.lineOffset + 1
        editor.cursorCol = editor.cursorCol + 1
      end
    end
       
    pos = pos - 1
  end
  updateColLine(editor)
end

function moveUp(editor)
  if editor.cursorLine>1 then
    editor.cursorLine = editor.cursorLine - 1  
    updatePositionsFromCursorPos(editor)
    updateColLine(editor)
  end
end

function moveDown(editor)
  if editor.cursorLine<editor.numLines then   
    editor.cursorLine = editor.cursorLine + 1
    updatePositionsFromCursorPos(editor)
    updateColLine(editor)
  end  
end

function delLine(editor, numLine)
  while numLine<editor.numLines do
    editor.lines[numLine] = editor.lines[numLine+1]
    numLine = numLine + 1
  end
  editor.lines[editor.numLines] = nil
  editor.numLines = editor.numLines - 1
end

function delChar(editor)
  local line = editor.lines[editor.cursorLine]
    
  local pre = ""
  if editor.lineOffset>1 then
    pre = string.sub(line, 1, editor.lineOffset-1)
  end
  
  local pos = ""
  if editor.lineOffset == string.len(line) then
    if editor.cursorLine < editor.numLines then
      pos = editor.lines[editor.cursorLine+1]
      delLine(editor, editor.cursorLine+1)
    end
  else
    pos = string.sub(line, editor.lineOffset+1)
  end
    
  editor.lines[editor.cursorLine] = pre .. pos
end

function backspace(editor)
  if (editor.cursorCol~=1) or (editor.cursorLine~=1) then
    moveLeft(editor)
    delChar(editor)
  end
end

function breakNewLine(editor)
  local line = editor.lines[editor.cursorLine]
  
  local pre = ""
  if editor.lineOffset>1 then
    pre = string.sub(line, 1, editor.lineOffset-1)
  end  
  local pos = string.sub(line, editor.lineOffset)
    
  local l = editor.numLines
  while l > editor.cursorLine do
    editor.lines[l+1] = editor.lines[l]
    l = l - 1
  end

  editor.lines[editor.cursorLine] = pre .. " "
  editor.lines[editor.cursorLine+1] = pos
  editor.numLines = editor.numLines + 1
     
  editor.cursorCol = 1
  editor.cursorLine = editor.cursorLine + 1
  editor.lineOffset = 1
  
  updateColLine(editor)
end

function addChar(editor, c)
  local line = editor.lines[editor.cursorLine]
  local tabByte = string.byte("\t")
  
  local pre = ""
  if editor.lineOffset>1 then
    pre = string.sub(line, 1, editor.lineOffset-1)
  end
  
  local pos = string.sub(line, editor.lineOffset)
  editor.lines[editor.cursorLine] = pre .. c .. pos
  
  if string.byte(c) == tabByte then
    editor.cursorCol = editor.cursorCol + editor.numTabSpaces
  else
    editor.cursorCol = editor.cursorCol + 1
  end
  editor.lineOffset = editor.lineOffset + 1

  updateColLine(editor)
end

--function drawCursor(editor)
--  local line = editor.lines[editor.cursorLine]
--  local tabByte = string.byte("\t")
  
--  local charsToPrint = string.sub(line, editor.lineOffset, editor.lineOffset)
     
--  if string.byte("\t") == string.byte(charsToPrint) then
--    charsToPrint = "\x1b[7m \x1b[27m"
--  else
--    charsToPrint = "\x1b[7m" .. charsToPrint .. "\x1b[27m"
--  end
    
--  local relativeX = editor.cursorCol - editor.col
--  local relativeY = editor.cursorLine - editor.line
     
--  console.puts(charsToPrint,relativeX,relativeY)
--end

function drawTextLine(editor, lineIndex, py)
  local line = editor.lines[lineIndex]
  local startCol = editor.col

  local lineToPrint = string.gsub(line, "\r\n", " ")
  lineToPrint = string.gsub(lineToPrint, "\n", " ")
  lineToPrint = string.gsub(lineToPrint, "\r", " ")
  lineToPrint = string.gsub(lineToPrint, "\t", string.rep(" ", editor.numTabSpaces) )

  local lineToPrintLen = string.len(lineToPrint)

  local substr = ""
  if startCol>lineToPrintLen then
    substr = string.rep(" ",editor.colsPerLine)
  else
    endCol = startCol + editor.colsPerLine-1
    if endCol>string.len(lineToPrint) then
      endCol = string.len(lineToPrint)
    end

    substr = string.sub(lineToPrint, startCol, endCol)

    local substrLen = endCol-startCol+1
    substr = substr .. string.rep(" ", editor.colsPerLine-substrLen)
  end

  if lineIndex==editor.cursorLine then
    local relativePos = editor.cursorCol - editor.col + 1

    local pre = ""
    if relativePos>1 then
      pre = string.sub(substr,1,relativePos-1)
    end

    local pos = ""
    if relativePos<editor.colsPerLine then
      pos = string.sub(substr,relativePos+1)
    end

    local cursorChar = "\x1b[7m" .. string.sub(substr,relativePos,relativePos) .. "\x1b[27m"
    substr = pre .. cursorChar .. pos
  end

  console_puts(substr,0,py/editor.fontH)
end

function drawTextLines(editor)
  local py = 0
  local l = editor.line
  
  while (py<editor.totalH) and (l<=editor.numLines) do
    drawTextLine(editor,l, py)
    py = py + editor.fontH
    l = l + 1
  end

  local empty = string.rep(" ", editor.colsPerLine)
  while py<editor.totalH do
    console_puts(empty,0,py/editor.fontH)
    py = py + editor.fontH
  end

--  drawCursor(editor)
end

function initEditor(fname)
  local editor = {}

  editor.fname = fname

  local numLines, lines = readLines(fname)
  editor.numLines = numLines
  editor.lines = lines

  editor.line = 1
  editor.col = 1

  editor.lineOffset = 1
  editor.cursorLine = 1
  editor.cursorCol = 1

  editor.totalW = 400
  editor.totalH = 240
  editor.fontW = 8
  editor.fontH = 8

  editor.numTabSpaces = 4

  editor.colsPerLine = editor.totalW/editor.fontW
  editor.linesPerScreen = editor.totalH/editor.fontH

  return editor
end

function initKeyb()
  local row1 = {}
  row1.x = 3
  row1.y = 5
  row1.btnW = 26
  row1.btnH = 45
  row1.btnS = 6
  row1.cnt = 10
  row1.data = {'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'}

  local row2 = {}
  row2.x = 19
  row2.y = 64
  row2.btnW = 26
  row2.btnH = 45
  row2.btnS = 6
  row2.cnt = 9
  row2.data = {'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'}

  local row3 = {}
  row3.x = 51
  row3.y = 123
  row3.btnW = 26
  row3.btnH = 45
  row3.btnS = 6
  row3.cnt = 7
  row3.data = {'z', 'x', 'c', 'v', 'b', 'n', 'm'}

  local row4 = {}
  row4.x = 3
  row4.y = 123
  row4.btnW = 39
  row4.btnH = 45
  row4.btnS = 236
  row4.cnt = 2
  row4.data = {'SHIFT', 'BACKSPACE'}

  local row5 = {}
  row5.x = 3
  row5.y = 183
  row5.btnW = 42
  row5.btnH = 45
  row5.btnS = 230
  row5.cnt = 2
  row5.data = {'NUM', 'ENTER'}

  local row6 = {}
  row6.x = 51
  row6.y = 183
  row6.btnW = 26
  row6.btnH = 45
  row6.btnS = 166
  row6.cnt = 2
  row6.data = {'/', '.'}

  local row7 = {}
  row7.x = 82
  row7.y = 183
  row7.btnW = 156
  row7.btnH = 45
  row7.btnS = 0
  row7.cnt = 1
  row7.data = {' '}

  local keyb = {}
  keyb.nRows = 7
  keyb.rows = {}
  keyb.rows[1] = row1
  keyb.rows[2] = row2
  keyb.rows[3] = row3
  keyb.rows[4] = row4
  keyb.rows[5] = row5
  keyb.rows[6] = row6
  keyb.rows[7] = row7

  keyb.img = Screen.loadImage("/teclado.png")
  
  return keyb
end

function initKeybShift()
  local row1 = {}
  row1.x = 3
  row1.y = 5
  row1.btnW = 26
  row1.btnH = 45
  row1.btnS = 6
  row1.cnt = 10
  row1.data = {'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'}

  local row2 = {}
  row2.x = 19
  row2.y = 64
  row2.btnW = 26
  row2.btnH = 45
  row2.btnS = 6
  row2.cnt = 9
  row2.data = {'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'}

  local row3 = {}
  row3.x = 51
  row3.y = 123
  row3.btnW = 26
  row3.btnH = 45
  row3.btnS = 6
  row3.cnt = 7
  row3.data = {'Z', 'X', 'C', 'V', 'B', 'N', 'M'}

  local row4 = {}
  row4.x = 3
  row4.y = 123
  row4.btnW = 39
  row4.btnH = 45
  row4.btnS = 236
  row4.cnt = 2
  row4.data = {'UNSHIFT', 'BACKSPACE'}

  local row5 = {}
  row5.x = 3
  row5.y = 183
  row5.btnW = 42
  row5.btnH = 45
  row5.btnS = 230
  row5.cnt = 2
  row5.data = {'NUM', 'ENTER'}

  local row6 = {}
  row6.x = 51
  row6.y = 183
  row6.btnW = 26
  row6.btnH = 45
  row6.btnS = 166
  row6.cnt = 2
  row6.data = {'/', '.'}

  local row7 = {}
  row7.x = 82
  row7.y = 183
  row7.btnW = 156
  row7.btnH = 45
  row7.btnS = 0
  row7.cnt = 1
  row7.data = {' '}

  local keyb = {}
  keyb.nRows = 7
  keyb.rows = {}
  keyb.rows[1] = row1
  keyb.rows[2] = row2
  keyb.rows[3] = row3
  keyb.rows[4] = row4
  keyb.rows[5] = row5
  keyb.rows[6] = row6
  keyb.rows[7] = row7

  keyb.img = Screen.loadImage("/teclado_shift.png")
  
  return keyb
end

function initKeybNum()
  local row1 = {}
  row1.x = 3
  row1.y = 5
  row1.btnW = 26
  row1.btnH = 45
  row1.btnS = 6
  row1.cnt = 10
  row1.data = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}

  local row2 = {}
  row2.x = 19
  row2.y = 64
  row2.btnW = 26
  row2.btnH = 45
  row2.btnS = 6
  row2.cnt = 9
  row2.data = {'@', '#', '$', '%', '&', '-', '+', '(', ')'}

  local row3 = {}
  row3.x = 51
  row3.y = 123
  row3.btnW = 26
  row3.btnH = 45
  row3.btnS = 6
  row3.cnt = 7
  row3.data = {'*', '"', "'", ':', ';', '!', '?'}

  local row4 = {}
  row4.x = 3
  row4.y = 123
  row4.btnW = 39
  row4.btnH = 45
  row4.btnS = 236
  row4.cnt = 2
  row4.data = {'SYM', 'BACKSPACE'}

  local row5 = {}
  row5.x = 3
  row5.y = 183
  row5.btnW = 42
  row5.btnH = 45
  row5.btnS = 230
  row5.cnt = 2
  row5.data = {'ABC', 'ENTER'}

  local keyb = {}
  keyb.nRows = 5
  keyb.rows = {}
  keyb.rows[1] = row1
  keyb.rows[2] = row2
  keyb.rows[3] = row3
  keyb.rows[4] = row4
  keyb.rows[5] = row5

  keyb.img = Screen.loadImage("/teclado_num.png")
  
  return keyb
end


function initKeybSym()
  local row1 = {}
  row1.x = 3
  row1.y = 5
  row1.btnW = 26
  row1.btnH = 45
  row1.btnS = 6
  row1.cnt = 10
  row1.data = {'~', '`', '|', ' ', ' ', ' ', ' ', ' ', ' ', ' '}

  local row2 = {}
  row2.x = 19
  row2.y = 64
  row2.btnW = 26
  row2.btnH = 45
  row2.btnS = 6
  row2.cnt = 9
  row2.data = {' ', ' ', ' ', ' ', '^', ' ', '=', '{', '}'}

  local row3 = {}
  row3.x = 51
  row3.y = 123
  row3.btnW = 26
  row3.btnH = 45
  row3.btnS = 6
  row3.cnt = 7
  row3.data = {'\\', ' ', ' ', ' ', ' ', '[', ']'}

  local row4 = {}
  row4.x = 3
  row4.y = 123
  row4.btnW = 39
  row4.btnH = 45
  row4.btnS = 236
  row4.cnt = 2
  row4.data = {'NUM', 'BACKSPACE'}

  local row5 = {}
  row5.x = 3
  row5.y = 183
  row5.btnW = 42
  row5.btnH = 45
  row5.btnS = 230
  row5.cnt = 2
  row5.data = {'ABC', 'ENTER'}

  local keyb = {}
  keyb.nRows = 5
  keyb.rows = {}
  keyb.rows[1] = row1
  keyb.rows[2] = row2
  keyb.rows[3] = row3
  keyb.rows[4] = row4
  keyb.rows[5] = row5

  keyb.img = Screen.loadImage("/teclado_sym.png")
  
  return keyb
end

function freeKeyb(keyb)
  Screen.freeImage(keyb.img)
end

function drawKeyb(keyb)
  Screen.drawImage(0,0,keyb.img,BOTTOM_SCREEN)
end

function readKeyb(keyb, tx, ty)
  local result = nil

  local green = Color.new(0,255,0)
  local i = 1
  while i<=keyb.nRows do
    local row = keyb.rows[i]
    if (ty >= row.y) and (ty <= row.y+row.btnH-1) then
      local px = row.x
      local j = 1
      while j<=row.cnt do
        if (tx >= px) and (tx <= px+row.btnW-1) then
          Screen.fillRect(px, px+row.btnW-1, row.y, row.y+row.btnH-1, green, BOTTOM_SCREEN)
          result = row.data[j]
          break
        end
        j = j + 1
        px = px + row.btnW + row.btnS
      end
    end
    i = i + 1
  end

  return result
end

local ACT_NONE = 0
local ACT_QUIT = 1
local ACT_SHOW_OPT = 2
local ACT_KEYB_ABC = 3
local ACT_KEYB_SHIFT = 4
local ACT_KEYB_NUM = 5
local ACT_KEYB_SYM = 6

local readInputBounce = 0
local lastKey = nil
function controlEditor(editor, keyb, pad, lastPad)
  local action = nil

  if lastPad ~= pad then
    readInputBounce = 0
    lastPad = pad
  end

  local tx,ty = Controls.readTouch()
  local key = readKeyb(keyb,tx,ty)

  if lastKey ~= key then
    readInputBounce = 0
    lastKey = key
  end


  if readInputBounce>1 then
    readInputBounce = readInputBounce - 1
    return ACT_NONE
  end

  if (Controls.check(pad,KEY_DUP)) then
    moveUp(editor)
    action = ACT_NONE
  end
  if (Controls.check(pad,KEY_DDOWN)) then
    moveDown(editor)
    action = ACT_NONE
  end
  if (Controls.check(pad,KEY_DLEFT)) then
    moveLeft(editor)
    action = ACT_NONE
  end
  if (Controls.check(pad,KEY_DRIGHT)) then
    moveRight(editor)
    action = ACT_NONE
  end
  if (Controls.check(pad,KEY_START)) then
    action = ACT_QUIT
  end

  if (Controls.check(pad,KEY_L)) then
    action = ACT_SHOW_OPT
  end
	
  if key=='SYM' then
    action = ACT_KEYB_SYM
  elseif key=='NUM' then
    action = ACT_KEYB_NUM
  elseif key=='ABC' then
    action = ACT_KEYB_ABC
  elseif key=='SHIFT' then
    action = ACT_KEYB_SHIFT
  elseif key=='UNSHIFT' then
    action = ACT_KEYB_ABC
  elseif key=='ENTER' then
    breakNewLine(editor)
    action = ACT_NONE
  elseif key=='BACKSPACE' then
    backspace(editor)
    action = ACT_NONE
  elseif key~=nil then
    addChar(editor, key)
    action = ACT_NONE
  end

  if action==nil then
    action = ACT_NONE
    readInputBounce = 0
  else
    if readInputBounce==0 then
      readInputBounce = 5
    else
      readInputBounce = 1
    end
  end

  return action
end

local currentOption = 1
function showOptions(options, numOptions, pad, lastPad)
  local result = nil

  if pad==lastPad then
    return nil
  end

  if (Controls.check(pad,KEY_DUP)) then
    if currentOption == 1 then
      currentOption = numOptions
    else
      currentOption = currentOption - 1
    end
  end
  if (Controls.check(pad,KEY_DDOWN)) then
    if currentOption == numOptions then
      currentOption = 1
    else
      currentOption = currentOption + 1
    end
  end
  if (Controls.check(pad,KEY_A)) then
    result = currentOption
    currentOption = 1
  end
  if (Controls.check(pad,KEY_B)) then
    result = 0
    currentOption = 1
  end

  printOptions(options, numOptions, currentOption)

  return result
end

function doOption(editor, option)
  if option=="Save" then
    save(editor)
  end
end


function main()
  Controls.init()
  local oldpad = Controls.read()

  local keyb = initKeyb()
  local editor = initEditor("/example.lua")

  local showOpts = false
  while true do
    Screen.refresh()

    Controls.init()
    local pad = Controls.read()

    drawKeyb(keyb)

    if showOpts then
      local options = {"Load","Save","CANCEL"}
      local selectedOption = showOptions(options,3,pad,oldpad)

      if selectedOption~=nil then
        doOption(editor, options[selectedOption])
        showOpts = false
      end
    else
      drawTextLines(editor)
      local action = controlEditor(editor, keyb, pad, oldpad)

      if action == ACT_QUIT then
        break
      elseif action == ACT_SHOW_OPT then
        showOpts = true
      elseif action == ACT_KEYB_ABC then
        freeKeyb(keyb)
        keyb = initKeyb()
      elseif action == ACT_KEYB_SHIFT then
        freeKeyb(keyb)
        keyb = initKeybShift()
      elseif action == ACT_KEYB_NUM then
        freeKeyb(keyb)
        keyb = initKeybNum()
      elseif action == ACT_KEYB_SYM then
        freeKeyb(keyb)
        keyb = initKeybSym()
      end
    end

    Screen.waitVblankStart()
    Screen.flip()

    oldpad = pad
  end

  freeKeyb(keyb)
end

main()
